package org.tea.mapper.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.text.NamingCase;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.tea.mapper.annotation.Column;
import org.tea.mapper.annotation.PrimaryKey;
import org.tea.mapper.annotation.TableName;

import java.lang.reflect.*;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 一些常用的工具类，用于识别表名、列名信息，以及一些通用方法相关的 SQL 的构建
 *
 *@author lxh
 */
public abstract class MapperHelper {

    private final static Log log = LogFactory.getLog(MapperHelper.class);

    public static void registerUserResultMap(SqlSessionFactory sqlSessionFactory) {
        Configuration configuration = sqlSessionFactory.getConfiguration();
        for (Class<?> mapper : configuration.getMapperRegistry().getMappers()) {
            registerUserResultMap(mapper, sqlSessionFactory);
        }
    }

    public static void registerUserResultMap(Class<?> mapperClazz, SqlSessionFactory sqlSessionFactory) {
        Configuration configuration = sqlSessionFactory.getConfiguration();


        // 构造 ResultMapping 列表
        Class<?> entityClazz = parseActualEntityType(mapperClazz);
        if (Objects.isNull(entityClazz)) {
            return;
        }

        Map<Pair<String, Class<?>>, String> field2Column = buildField2Column(entityClazz);
        Method[] ms = ReflectUtil.getMethods(mapperClazz);
        for (Method method : ms) {
            SelectProvider selectProvider = method.getAnnotation(SelectProvider.class);
            if (Objects.isNull(selectProvider)) {
                continue;
            }
            if (!(MapperHelper.class.isAssignableFrom(selectProvider.type()))) {
                continue;
            }

            List<ResultMapping> resultMappings = new ArrayList<>();
            Set<String> mappedColumns = new HashSet<>();
            for (Map.Entry<Pair<String, Class<?>>, String> entry : field2Column.entrySet()) {
                String columnName = entry.getValue();
                Pair<String, Class<?>> pair = entry.getKey();
                ResultMapping resultMapping = new ResultMapping.Builder(configuration, pair.getKey(), columnName, pair.getValue())
                        .build();
                resultMappings.add(resultMapping);
                mappedColumns.add(columnName.toUpperCase(Locale.ENGLISH));
            }

            // 构造 ResultMap
            String resultMapId = genResultMapId(mapperClazz, method);
            if (configuration.hasResultMap(resultMapId)) {
                ResultMap resultMap = configuration.getResultMap(resultMapId);
                ReflectUtil.setFieldValue(resultMap, "propertyResultMappings", Collections.unmodifiableList(resultMappings));
                ReflectUtil.setFieldValue(resultMap, "resultMappings", Collections.unmodifiableList(resultMappings));
                ReflectUtil.setFieldValue(resultMap, "mappedColumns", Collections.unmodifiableSet(mappedColumns));
                continue;
            }

            ResultMap resultMap = new ResultMap.Builder(configuration, resultMapId, entityClazz, resultMappings)
                    .build();

            // 注册到 Configuration
            configuration.addResultMap(resultMap);
        }
    }

    protected static String genResultMapId(Class<?> mapperClazz, Method method) {
        StringBuilder suffix = new StringBuilder();
        for (Class<?> c : method.getParameterTypes()) {
            suffix.append("-");
            suffix.append(c.getSimpleName());
        }
        if (suffix.length() < 1) {
            suffix.append("-void");
        }
        return mapperClazz.getName() + "." + method.getName() + suffix;
    }

    protected static Class<?> parseActualEntityType(Class<?> mapperClazz) {
        Type[] interfaces = mapperClazz.getGenericInterfaces();
        for (Type type : interfaces) {
            if (type instanceof ParameterizedType) {
                ParameterizedType pt = (ParameterizedType) type;
                Type entityType = pt.getActualTypeArguments()[0];
                log.trace(String.format("%s actual entity type: %s", mapperClazz, entityType));
                return (Class<?>) entityType;
            }
        }
        throw new IllegalArgumentException("无法找到对应的实体类型: " + mapperClazz.getName());
    }

    protected static Map<Pair<String, Class<?>>, String> buildField2Column(Class<?> entityClazz) {
        Field[] fields = ReflectUtil.getFields(entityClazz);
        Map<Pair<String, Class<?>>, String> ans = new HashMap<>();
        for (Field field : fields) {
            Column column = field.getAnnotation(Column.class);
            if (Objects.isNull(column)) continue;
            ans.put(Pair.of(field.getName(), field.getType()), column.value());
        }
        return ans;
    }

    protected static List<String> parseColumns(Class<?> entityClazz) {
        Field[] fields = ReflectUtil.getFields(entityClazz);
        List<String> ans = new ArrayList<>();
        for (Field field : fields) {
            Column column = field.getAnnotation(Column.class);
            if (Objects.isNull(column)) continue;
            ans.add(column.value());
        }
        return ans;
    }

    protected static Map<String, String> parsePrimaryKey2FieldName(Class<?> entityClazz) {
        Field[] fields = ReflectUtil.getFields(entityClazz);
        Map<String, String> pk2FieldName = new HashMap<>();
        for (Field field : fields) {
            PrimaryKey primaryKey = field.getAnnotation(PrimaryKey.class);
            Column column = field.getAnnotation(Column.class);
            if (Objects.isNull(primaryKey)) continue;
            if (Objects.isNull(column)) {
                throw new IllegalArgumentException(
                        String.format("不正确的配置，字段：%s 已经配置配置了主键，但是没有配置映射的列名", field.getName())
                );
            }
            pk2FieldName.put(column.value(), field.getName());
        }
        return pk2FieldName;
    }

    protected static List<String> parsePrimaryKeyColumns(Class<?> entityClazz) {
        Field[] fields = ReflectUtil.getFields(entityClazz);
        List<String> ans = new ArrayList<>();
        for (Field field : fields) {
            PrimaryKey primaryKey = field.getAnnotation(PrimaryKey.class);
            Column column = field.getAnnotation(Column.class);
            if (Objects.isNull(primaryKey)) continue;
            if (Objects.isNull(column)) {
                throw new IllegalArgumentException(
                        String.format("不正确的配置，字段：%s 已经配置配置了主键，但是没有配置映射的列名", field.getName())
                );
            }
            ans.add(column.value());
        }
        return ans;
    }

    protected static String parseTableName(Class<?> entityClazz) {
        TableName tableNameAnno = entityClazz.getAnnotation(TableName.class);
        if (!Objects.isNull(tableNameAnno)) {
            return tableNameAnno.tableName();
        }
        return NamingCase.toUnderlineCase(entityClazz.getSimpleName());
    }

    protected static String buildWhereCondition(Class<?> entityType) {
        Map<Pair<String, Class<?>>, String> field2Column = buildField2Column(entityType);
        String tableName = parseTableName(entityType);

        StringBuilder whereSqlBuilder = new StringBuilder();
        for (Map.Entry<Pair<String, Class<?>>, String> entry : field2Column.entrySet()) {
            Pair<String, Class<?>> key = entry.getKey();
            if (key.getValue() == null) continue;

            String fieldName = key.getKey();
            String columnName = entry.getValue();
            if (key.getValue().isAssignableFrom(Collection.class)) {
                whereSqlBuilder.append("<if test='").append("param.").append(fieldName)
                        .append(" != null and param.").append(fieldName).append(".size > 0'>").append("\n")
                        .append("\t").append(" AND ").append(tableName).append(".")
                        .append(columnName).append(" IN ").append("\n")
                        .append("<foreach collection='").append("param.").append(fieldName)
                        .append("' item='item' open='(' close=')' separator=','>").append("\n")
                        .append("\t").append("#{item}").append("\n")
                        .append("</foreach>")
                        .append("</if>");
            } else if (key.getValue().isAssignableFrom(String.class)) {
                whereSqlBuilder.append("<if test='").append("param.").append(fieldName)
                        .append(" != null and param.").append(fieldName).append(".trim.length > 0'>").append("\n")
                        .append("\t").append(" AND ").append(tableName).append(".").append(columnName).append("=")
                        .append("#{param.").append(fieldName).append("}").append("\n")
                        .append("</if>");
            } else {
                whereSqlBuilder.append("<if test='").append("param.").append(fieldName)
                        .append(" != null ").append("'>").append("\n")
                        .append("\t").append(" AND ").append(tableName).append(".").append(columnName).append("=")
                        .append("#{param.").append(fieldName).append("}").append("\n")
                        .append("</if>");
            }
            whereSqlBuilder.append("\n");
        }
        log.trace(String.format("buildWhereCondition sql: %s", whereSqlBuilder));
        return whereSqlBuilder.toString();
    }

    protected static String buildUpdateWhereSql(Class<?> entityType, List<?> rowList) {
        Map<Pair<String, Class<?>>, String> field2Column = buildField2Column(entityType);
        Map<String, Set<String>> colCondMap = new HashMap<>();
        StringBuilder whereSqlBuilder = new StringBuilder();
        whereSqlBuilder.append("<where>");
        for (Map.Entry<Pair<String, Class<?>>, String> entry : field2Column.entrySet()) {
            Pair<String, Class<?>> pair = entry.getKey();
            String columnName = entry.getValue();
            colCondMap.computeIfAbsent(columnName, any -> new HashSet<>());
            for (Object row : rowList) {
                Object value = ReflectUtil.getFieldValue(row, pair.getKey());
                if (value == null) {
                    continue;
                }
                colCondMap.get(columnName).add("'" + convertObjValToStr(value) + "'");
            }
        }
        whereSqlBuilder.append(combineConditionValues(colCondMap));
        whereSqlBuilder.append("</where>");

        log.trace(String.format("buildUpdateWhereSql sql: %s", whereSqlBuilder));
        return whereSqlBuilder.toString();
    }

    protected static String buildPkWhereSql(Class<?> entityType, Object param) {
        Map<String, String> pk2FieldMap = parsePrimaryKey2FieldName(entityType);
        StringBuilder whereSqlBuilder = new StringBuilder();
        whereSqlBuilder.append("<where>");
        for (Map.Entry<String, String> entry : pk2FieldMap.entrySet()) {
            Object actualParam = param;
            if (param instanceof MapperMethod.ParamMap) {
                actualParam = ((MapperMethod.ParamMap<?>) param).get("param");
            }
            if (actualParam instanceof Collection) {
                String sql = "<if test=\"" + "param" + " != null and param.size > 0\">" + "\n" +
                        " AND " + entry.getKey() + " IN " + "\n" +
                        "<foreach collection=\"" + "param" + "\" item=\"item\" open='(' close=')' separator=','>" + "\n" +
                        "\t" + "#{item." + entry.getValue() + "} " + "\n" +
                        "</foreach>" +
                        "</if>";
                whereSqlBuilder.append(sql);
                continue;
            }

            String sql = "<if test=\"" + "param." + entry.getValue() + " != null \">" + "\n" +
                    " AND " + entry.getKey() + " = " + "#{param." + entry.getValue() + "} " + "\n" +
                    "</if>";
            whereSqlBuilder.append(sql);
        }
        whereSqlBuilder.append("</where>");

        log.trace(String.format("buildUpdateWhereSql sql: %s", whereSqlBuilder));
        return whereSqlBuilder.toString();
    }

    private static String combineConditionValues(Map<String, Set<String>> colCondMap) {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, Set<String>> entry : colCondMap.entrySet()) {
            if (CollUtil.isEmpty(entry.getValue())) continue;

            sb.append(" AND ").append(entry.getKey()).append(" IN ")
                    .append("(")
                    .append(String.join(", ", entry.getValue()))
                    .append(")");
        }
        return sb.toString();
    }

    protected static String convertObjValToStr(Object obj) {
        if (Objects.isNull(obj)) {
            return null;
        }
        if (obj instanceof String) {
            return (String) obj;
        }
        if (obj instanceof Date) {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            return dateFormat.format(obj);
        }
        return String.valueOf(obj);
    }
}
